import copy
import dataclasses as dcl
from collections.abc import MutableSequence
from functools import partialmethod
from typing import List

from omegaconf import OmegaConf
from .errors import ZS_ValidationError, ValidationError
from .validation import validate_fields, check_node, is_leaf_node

class SchemaProc:
    def __init__(self, schema_obj):
        self.root_class = None
        self.schema_tree = None
        self.required_nodes = []
        self.convert_nodes = []
        try:
            self.root_class = self._proc_node(schema_obj)[1][1]
            self.schema_tree = OmegaConf.structured(self.root_class)
        except ZS_ValidationError:
            raise
        except ValidationError as e:
            raise ZS_ValidationError(verr=e) from None

    def _proc_node(self, node, name='', path=None):
        path = [] if (path is None) else (path + [name])
        (is_cfglist, is_leaf, has_child, child_keys, n_type,
         n_def, n_required, conv_func, vspecs) = check_node(node, path)

        if is_leaf:
            if is_cfglist:
                raise ZS_ValidationError(
                        msg='Leaf node cannot be CfgList', path=path)

            if has_child:
                raise ZS_ValidationError(
                        msg='Leaf node cannot have child nodes', path=path)

            if n_type is None:
                raise ZS_ValidationError(
                        msg='Leaf node must define valid "_type"', path=path)

            if n_required:
                self.required_nodes.append(path)

            if conv_func is not None:
                self.convert_nodes.append((path, conv_func))

            kwargs = ({'default_factory': lambda: n_def.copy()}
                      if isinstance(n_def, MutableSequence)
                      else {'default': n_def})
            def_spec = [dcl.field(**kwargs)]

            return [vspecs, tuple(([name, n_type] + def_spec))]

        elif has_child:
            child_nodes = [self._proc_node(node[k], k, path)
                           for k in child_keys]
            child_vspecs = [x[0] for x in child_nodes if (x[0] is not None)]
            child_fields = [x[1] for x in child_nodes]
            clname = 'DCL__' + '__'.join(path)
            ns = {'__post_init__': partialmethod(validate_fields,
                                                 vspecs=child_vspecs)}
            dclass = dcl.make_dataclass(clname, child_fields, namespace=ns)
            defval = dclass()
            if is_cfglist:
                # CfgList node is a list of the subtrees
                dclass = List[dclass]
                defval = dcl.field(default_factory=list)

            return [None, (name, dclass, defval)]

def _merge_2(d1, d2):
    for k in d2.keys():
        if (k in d1) and isinstance(d1[k], dict) and isinstance(d2[k], dict):
            is_leaf1 = is_leaf_node(d1[k])
            is_leaf2 = is_leaf_node(d2[k])
            if not is_leaf1 and not is_leaf2:
                _merge_2(d1[k], d2[k])
                continue

        d1[k] = d2[k]

def _merge_all(l_dicts):
    ret = copy.deepcopy(l_dicts[0])
    for d in l_dicts[1:]:
        _merge_2(ret, d)

    return ret

def process_schemas(l_schema_objs):
    # try:
    merged = _merge_all([x['obj'] for x in l_schema_objs])
    print(merged)
    schema_tree_obj = SchemaProc(merged)
    return schema_tree_obj
    # except Exception as e:
    #     raise Exception(f'Error processing schemas: {str(e)}') from None

